YownYang's blog

Property和KVO的爱恨纠葛

这是一篇Property和KVO爱恨纠葛的文章。

前言

由于系统KVO没有block回调,所以本人通过模拟系统常规KVO的实现方式去进行了block的添加,也就是常见的重写被监听对象的setter方法。正常情况下一切顺利,然而当自定义的KVO遇到readonly时,就发生了一些好玩的事情。

KVO

KVO概述

KVO的全称是keyValueObserving,也就是观察者模式在iOS的实现之一。KVO的作用就是用来监听某个属性值的变化或者集合中元素的变化。

KVO被触发的原因

表面原因:

  1. 对于普通属性变化,只需要通过属性的setter方法改变值就可以了;
  2. 对于readonly属性变化,则有两种办法:一是调用willChangeValueForKeydidChangeValueForKey;二是使用KVC;
  3. 对于数组元素的变化,需要在对元素增删替代时,使用mutableArrayValueForKey方法。字典集合等都有其对应的方法。

根本原因:

通过不同的手段最终触发了NSKeyValueNotifyObserver函数。这个函数是个内部函数,但你可以在observeValueForKeyPath:ofObject:change:context:中打断点,然后再调用栈中看到它。

自定义KVO的实现原理

  1. 建立被观察对象所属类的子类;
  2. 将被观察对象所属的实例的类指向创建的子类;
  3. 重写被观察对象的setter方法,在其中触发你传进来的block;如果是系统的话,就是触发willChangeValueForKeydidChangeValueForKey

监听readonly的特质的属性?

众所周知,如果你监听的属性是只读的,那么KVO毫无作用,因为只读属性是不存在setter方法的。

那么,不知道你有没有关注过WKWebViewtitlecanGoBackestimatedProgress等属性呢?这些属性都是只读属性,但它们可以触发KVO。下面是WKWebViewtitle的官方文档:

1
2
3
4
5
/*! @abstract The page title.
@discussion @link WKWebView @/link is key-value observing (KVO) compliant
for this property.
*/
@property (nullable, nonatomic, readonly, copy) NSString *title;

自定义KVO的问题

实现方式一:重写setter方法

如果你仔细看了上面的文字,其实就大概可以猜到WKWebView的这些只读属性是如何触发KVO的了。

这个时候问题就来了,当你使用自己定义的KVO时,你并不能获取到数据的更改了。因为你是基于setter方法的重写,而只读属性是绕过了setter方法,直接使用了上述针对readonly的两种办法。

这个时候你基于你现有的自定义实现毫无办法去解决这个问题,那么就只能抛弃自定义实现喽。

实现方式二:重写observeValueForKeyPath:ofObject:change:context:方法

基于这种实现方式的轮子,其实facebook已经实现了,那就是KVOController。有兴趣的话可以去github上看下,链接在此

它的实现方式大致就是通过重写observeValueForKeyPath:ofObject:change:context:方法来进行监听分发。由于触发KVO的根本原因是触发了NSKeyValueNotifyObserver函数,这个函数会调用observeValueForKeyPath:ofObject:change:context:,所以基于这种实现的自定义KVO可以接收到任何的KVO触发。

总结

感觉这个知识点好短啊,总结就两句话。

  1. 如果自定义KVO,就考虑使用Facebook那一套或者以observeValueForKeyPath:ofObject:change:context:方法为实现基础。
  2. 老老实实使用系统提供的KVO。